package taskprocess

import (
	"acs/taskprocess/storage"
	"fmt"
	"time"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const (
	// DbCollectionUserTasks is the storage collection name of "user tasks"
	DbCollectionUserTasks = "user_tasks"
	// DbCollectionTaskContents is the storage collection name of "task contents"
	DbCollectionTaskContents = "task_contents"
)

var timeOffset time.Time

// UserTaskPersist the task that to be persisted in solid storage for the user.
type UserTaskPersist struct {
	Type     TaskType
	BundleID string
	Platform AppPlatform
	UID      string
	TaskNo   int64
	Status   int
	UUID     string

	//过滤条件
	Appver [2]string
	BundleIds []string
	System []int
	OSVersion [2]string
	Brand []string
	Model []string
}

// PatchTaskPersist is to be persisted in solid storage.
type PatchTaskPersist struct {
	Type   TaskType
	TaskNo int64
	// Content is one of the task type structs.
	Content *PatchTaskToStore
}

// SchemaTaskPersist is to be persisted in solid storage.
type SchemaTaskPersist struct {
	Type   TaskType
	TaskNo int64
	// Content is one of the task type structs.
	Content *SchemaTaskToStore
}

type EventTaskPersist struct {
	Type   TaskType
	TaskNo int64
	// Content is one of the task type structs.
	Content *EventTaskToStore
}


type taskContentID struct {
	ID bson.ObjectId `bson:"_id"`
}

func initPersistStorage() {
	var (
		err  error
		db   *mgo.Database
		cols []string
	)
	defer func() {
		if err != nil {
			panic(logger.Criticalf("Initial storage failed: %v", err))
		}
	}()
	timeOffset, _ = time.Parse("2006/01/02 15:04:05", "2016/08/17 03:00:00")
	db, err = getDb()
	if err != nil {
		return
	}
	cols, err = db.CollectionNames()
	if err != nil {
		return
	}
	var userTaskColExists, taskContentColsExists bool
	for _, col := range cols {
		if col == DbCollectionTaskContents {
			taskContentColsExists = true
		} else if col == DbCollectionUserTasks {
			userTaskColExists = true
		}
	}

	if !userTaskColExists {
		logger.Infof("initializing storage collection[%v]...", DbCollectionUserTasks)
		err = db.C(DbCollectionUserTasks).EnsureIndex(
			mgo.Index{Key: []string{"uid", "taskno"}, Unique: true},
		)
		if err != nil {
			return
		}
		err = db.C(DbCollectionUserTasks).EnsureIndex(
			mgo.Index{Key: []string{"uid", "taskno", "type"}},
		)
		if err != nil {
			return
		}
	}
	if !taskContentColsExists {
		logger.Infof("initializing storage collection[%v]...", DbCollectionTaskContents)
		err = db.C(DbCollectionTaskContents).EnsureIndex(
			mgo.Index{Key: []string{"-taskno"}, Unique: true},
		)
		if err != nil {
			return
		}
		err = db.C(DbCollectionTaskContents).EnsureIndex(
			mgo.Index{Key: []string{"taskno", "content.bundleid", "content.platform"}},
		)
		if err != nil {
			return
		}
		err = db.C(DbCollectionTaskContents).EnsureIndex(
			mgo.Index{Key: []string{"content.bundleid", "content.platform"}},
		)
	}
}

func getDb() (*mgo.Database, error) {
	ses, err := storage.GetConn(cfg.Storage.Dsn)
	if err != nil {
		return nil, err
	}
	return ses.DB(cfg.Storage.Database), nil
}

// StoreTaskToDb saves tasks to persistent storage.
// task is a pointer to one of the task type structs.
func StoreTaskToDb(task interface{}) error {
	db, err := getDb()
	if err != nil {
		return err
	}
	var newTask interface{}
	var taskUids *[]string
	var taskType TaskType
	var taskNo = genTaskNo()
	var taskPlatform AppPlatform
	var bundleID string
	switch task.(type) {
	case *PatchTask:
		nt := new(PatchTaskPersist)
		taskType = TaskTypePatch
		nt.Type = taskType
		taskI := task.(*PatchTask)
		nt.Content = &taskI.PatchTaskToStore
		nt.TaskNo = taskNo
		n, err := db.C(DbCollectionTaskContents).Find(bson.M{"content.md5": nt.Content.Md5, "content.patchid": nt.Content.PatchID}).Count()
		if err != nil {
			if err != storage.ErrDataNotFound {
				return err
			}
		} else if n > 0 {
			return fmt.Errorf("Duplicated task for app[%v] patch[%v] md5[%v]", nt.Content.BundleID, nt.Content.PatchID, nt.Content.Md5)
		}
		newTask = nt
		taskUids = &taskI.TargetUID
		taskPlatform = nt.Content.Platform
		bundleID = nt.Content.BundleID
	case *SchemaTask:
		nt := new(SchemaTaskPersist)
		taskType = TaskTypeSchema
		nt.Type = taskType
		taskI := task.(*SchemaTask)
		nt.Content = &taskI.SchemaTaskToStore
		nt.TaskNo = taskNo
		n, err := db.C(DbCollectionTaskContents).Find(bson.M{"content.md5": nt.Content.Md5, "content.schemaid": nt.Content.SchemaID}).Count()
		if err != nil {
			if err != storage.ErrDataNotFound {
				return err
			}
		} else if n > 0 {
			return fmt.Errorf("Duplicated task for app[%v] schema[%v] md5[%v]", nt.Content.BundleID, nt.Content.SchemaID, nt.Content.Md5)
		}
		newTask = nt
		taskUids = &taskI.TargetUID
		taskPlatform = nt.Content.Platform
		bundleID = nt.Content.BundleID
	default:
		return fmt.Errorf("Invalid task type %#v", task)
	}

	err = db.C(DbCollectionTaskContents).Insert(newTask)
	if err != nil {
		return err
	}

	tid := new(taskContentID)
	err = db.C(DbCollectionTaskContents).Find(bson.M{"taskno": taskNo}).Select(bson.M{"_id": 1}).One(tid)
	if err != nil {
		return fmt.Errorf("failed to get stored task[%v]. ERR: %v", taskNo, err)
	}
	var userTask *UserTaskPersist
	for _, uid := range *taskUids {
		userTask = &UserTaskPersist{
			Type:     taskType,
			UID:      uid,
			TaskNo:   taskNo,
			BundleID: bundleID,
			Platform: taskPlatform,
		}
		err = db.C(DbCollectionUserTasks).Insert(userTask)
		if err != nil {
			return fmt.Errorf("Failed to store user task[%v]. err: %v", taskNo, err)
		}
	}
	return nil
}

func genTaskNo() int64 {
	return time.Now().Sub(timeOffset).Nanoseconds()
}


func StoreEventTaskToDb(eventTaskToStore *EventTaskToStore, task *EventTask, taskIds []string, status int) error  {
	db, err := getDb()
	if err != nil {
		return err
	}

	var taskNo = genTaskNo()
	nt := new(EventTaskPersist)
	nt.Type = TaskTypeEvent
	nt.TaskNo = taskNo
	nt.Content = eventTaskToStore

	err = db.C(DbCollectionTaskContents).Insert(nt)
	if err != nil {
		return err
	}

	tid := new(taskContentID)
	err = db.C(DbCollectionTaskContents).Find(bson.M{"taskno": taskNo}).Select(bson.M{"_id": 1}).One(tid)
	if err != nil {
		return fmt.Errorf("failed to get stored task[%v]. ERR: %v", taskNo, err)
	}
	var userTask *UserTaskPersist
	for _, uid := range taskIds {
		userTask = &UserTaskPersist{
			Type:     TaskTypeEvent,
			UID:      uid,
			TaskNo:   taskNo,
			Status:   status,

			Appver: task.Appver,
			BundleIds: task.BundleIds,
			System: task.System,
			OSVersion: task.OSVersion,
			Brand: task.Brand,
			Model: task.Model,
		}
		err = db.C(DbCollectionUserTasks).Insert(userTask)
		if err != nil {
			return fmt.Errorf("Failed to store user task[%v]. err: %v", taskNo, err)
		}
	}

	return nil
}